/*
 * Copyright (C) 2013 Daniel Treffenstädt <daniel@treffenstaedt.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "OMap.h"

#include "../Core/OLog.h"

#include <QDataStream>

O_START_NAMESPACE

Map::Map() :
	m_width(0),
	m_height(0),
	m_resolution(0),
	m_cubeSize(0),
	m_loadDistance(0),
	m_fileName(""),
	m_file(0)
{}

Map::Map(String fileName) :
	m_width(0),
	m_height(0),
	m_resolution(0),
	m_cubeSize(0),
	m_loadDistance(0),
	m_fileName(fileName),
	m_file(0)
{
	m_file	= new QFile(fileName.c_str());
	if (!m_file->open(QFile::ReadWrite))
	{
		error("Could not open World Map file.");
	}
	loadHeader();
}

Map::~Map()
{
	if (m_file != 0)
	{
		m_file->close();
		delete m_file;
	}
	QHash<short, QHash<short, QHash<short, Cube*> > >::Iterator x;
	QHash<short, QHash<short, Cube*> >::Iterator y;
	QHash<short, Cube*>::Iterator z;

	for (x = m_cubes.begin(); x != m_cubes.end(); x++)
	{
		for (y = x.value().begin(); y != x.value().end(); y++)
		{
			for (z = y.value().begin(); z != y.value().end(); z++)
			{
				delete z.value();
			}
		}
	}
}

bool Map::saveHeader()
{
	if (m_file == 0)
	{
		return false;
	}
	m_file->seek(0);
	QDataStream stream(m_file);

	stream<<m_width;
	stream<<m_height;
	stream<<m_resolution;
	stream<<m_cubeSize;
	stream<<m_loadDistance;

	return true;
}

void Map::loadHeader()
{
	if (m_file->bytesAvailable() >= 10)
	{
		QDataStream stream(m_file);

		stream>>m_width;
		stream>>m_height;
		stream>>m_resolution;
		stream>>m_cubeSize;
		stream>>m_loadDistance;
	}
}

void Map::setWidth(short width)
{
	m_width	= width;
}

void Map::setheigth(short height)
{
	m_height	= height;
}

void Map::setResolution(short size)
{
	m_resolution	= size;
}

void Map::setCubeSize(short size)
{
	m_cubeSize	= size;
}

void Map::setLoadDistance(short d)
{
	m_loadDistance	= d;
}

bool Map::setFileName(String fileName)
{
	m_fileName	= fileName;
	if (m_file != 0)
	{
		m_file->close();
		delete m_file;
	}
	m_file	= new QFile(fileName.c_str());
	if (!m_file->open(QFile::ReadWrite))
	{
		return false;
	}
	loadHeader();
	return true;
}

short Map::width()
{
	return m_width;
}

short Map::height()
{
	return m_height;
}

short Map::resolution()
{
	return m_resolution;
}

short Map::cubeSize()
{
	return m_cubeSize;
}

String Map::fileName()
{
	return m_fileName;
}

unsigned long long Map::byteOffset(short x, short y, short z)
{
	return ((unsigned long long) 10) + ((unsigned long long) (m_width * y - (m_width - x) + (z * m_width * m_height))) * ((unsigned long long) (1 + 6*(3*(m_resolution^2))));
}

bool Map::load(short x, short y, short z)
{
	unsigned long long offset	= byteOffset(x, y, z);
	if (!m_file->seek(offset))
	{
		return false;
	}

	bool water;

	QDataStream stream(m_file);

	stream>>water;

	Cube* cube	= new Cube(x, y, z, water);


	bool visible;
	short** height		= new short*[m_resolution];
	bool** traversion	= new bool*[m_resolution];
	short base;
	short top;

	for (int i = 0; i < 6; i++)
	{
		visible	= false;
		stream>>visible;
		stream>>base;
		stream>>top;
		for (int j = 0; j < m_resolution; j++)
		{
			height[j]		= new short[m_resolution];
			traversion[j]	= new bool[m_resolution];
			for (int k = 0; k < m_resolution; k++)
			{
				height[j][k]		= 0;
				traversion[j][k]	= false;

				stream>>height[j][k];
				stream>>traversion[j][k];
			}
		}

		cube->setFace(new Face(m_resolution, height, traversion, (Face::Position) i, base, top, visible));
	}

	setCube(cube);
	return true;
}

void Map::setCube(Cube *c)
{
	short x	= c->x();
	short y	= c->y();
	short z	= c->z();

	m_cubes[x][y][z]	= c;
}

Cube* Map::getCube(short x, short y, short z)
{
	return m_cubes[x][y][z];
}

bool Map::saveAll()
{
	QHash<short, QHash<short, QHash<short, Cube*> > >::Iterator x;
	QHash<short, QHash<short, Cube*> >::Iterator y;
	QHash<short, Cube*>::Iterator z;

	for (x = m_cubes.begin(); x != m_cubes.end(); x++)
	{
		for (y = x.value().begin(); y != x.value().end(); y++)
		{
			for (z = y.value().begin(); z != y.value().end(); z++)
			{
				if (!save(x.key(), y.key(), z.key()))
				{
					return false;
				}
			}
		}
	}
	return true;
}

bool Map::save(short x, short y, short z)
{
	Cube* c	= getCube(x, y, z);
	unsigned long long offset	= byteOffset(x, y, z);
	if (!m_file->seek(offset))
	{
		return false;
	}

	QDataStream stream(m_file);

	stream<<c->isWater();


	for (int i = 0; i < 6; i++)
	{
		Face* f	= c->getFace((Face::Position) i);
		stream<<f->visible();
		stream<<f->base();
		stream<<f->top();
		for (int j = 0; j < m_resolution; j++)
		{
			for (int k = 0; k < m_resolution; k++)
			{
				stream<<f->heightAt(j, k);
				stream<<f->getTraversion(j, k);
			}
		}
	}
	return true;
}

bool Map::loadArea(Vector3D position)
{
	short x		= ((int) position.x()) % m_cubeSize;
	short y		= ((int) position.y()) % m_cubeSize;
	short z		= ((int) position.z()) % m_cubeSize;
	short minX	= x - m_loadDistance;
	short minY	= y - m_loadDistance;
	short minZ	= z - m_loadDistance;
	short maxX	= x + m_loadDistance;
	short maxY	= y + m_loadDistance;
	short maxZ	= z + m_loadDistance;

	for (short i = minX; i < maxX; i++)
	{
		for (short j = minY; j < maxY; j++)
		{
			for (short k = minZ; k < maxZ; k++)
			{
				if (!load(i, j, k))
				{
					return false;
				}
			}
		}
	}
	return true;
}

O_END_NAMESPACE
